﻿using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml;
using System.Collections.ObjectModel;
using System.Xml.Linq;
using System.Linq;
using System.Data;


namespace VAdvantage.DataUtil
{
	public class DynamicDataBuilder
	{
        private static System.Type BuildDataObjectType(ObservableCollection<DataService.DataColumnInfo> Columns, string DataObjectName)
        {
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("AprimoDynamicData"), AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DataModule");

            TypeBuilder tb = moduleBuilder.DefineType(DataObjectName,
                                                    TypeAttributes.Public |
                                                    TypeAttributes.Class,
                                                    typeof(DataObject));

            ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
            foreach (var col in Columns)
            {
                string propertyName = col.ColumnName.Replace(' ', '_').ToUpper();
                Type dataType = System.Type.GetType(col.DataTypeName, false, true);				
                if (dataType != null)
                {
                    FieldBuilder fb = tb.DefineField("_" + propertyName, dataType, FieldAttributes.Private);
                    PropertyBuilder pb = tb.DefineProperty(propertyName, PropertyAttributes.None,dataType, null);
                    MethodBuilder getMethod = tb.DefineMethod("get_" + propertyName,
                                                                MethodAttributes.Public |
                                                                MethodAttributes.HideBySig |
                                                                MethodAttributes.SpecialName,
                                                                dataType, 
                                                                Type.EmptyTypes);

                    ILGenerator ilgen = getMethod.GetILGenerator();
                    //Emit Get property, return _prop
                    ilgen.Emit(OpCodes.Ldarg_0);
                    ilgen.Emit(OpCodes.Ldfld, fb);
                    ilgen.Emit(OpCodes.Ret);
                    pb.SetGetMethod(getMethod);
                    MethodBuilder setMethod = tb.DefineMethod("set_" + propertyName,
                    MethodAttributes.Public |
                    MethodAttributes.HideBySig |
                    MethodAttributes.SpecialName,
                    null,
                    new Type[] { dataType });
                    ilgen = setMethod.GetILGenerator();
                    LocalBuilder localBuilder = ilgen.DeclareLocal(typeof(String[]));
                    //Emit set property, _Prop = value;
                    ilgen.Emit(OpCodes.Ldarg_0);
                    ilgen.Emit(OpCodes.Ldarg_1);
                    ilgen.Emit(OpCodes.Stfld, fb);

                    //Notify Change:
                    Type[] wlParams = new Type[] { typeof(string[]) };
                    MethodInfo notifyMI = typeof(DataObject).GetMethod("NotifyChange",
                    BindingFlags.NonPublic |
                    BindingFlags.Instance,
                    null,
                    CallingConventions.HasThis,
                    wlParams,
                    null);

                    //NotifyChange Property change
                    ilgen.Emit(OpCodes.Ldc_I4_1);
                    ilgen.Emit(OpCodes.Newarr, typeof(String));
                    ilgen.Emit(OpCodes.Stloc_0);
                    ilgen.Emit(OpCodes.Ldloc_0);
                    ilgen.Emit(OpCodes.Ldc_I4_0);
                    ilgen.Emit(OpCodes.Ldstr, propertyName);
                    ilgen.Emit(OpCodes.Stelem_Ref);
                    ilgen.Emit(OpCodes.Ldarg_0);
                    ilgen.Emit(OpCodes.Ldloc_0);
                    ilgen.EmitCall(OpCodes.Call, notifyMI, null); // call nodifyChange function

                    ilgen.Emit(OpCodes.Ret);
                    pb.SetSetMethod(setMethod);
                }
            }
            System.Type rowType = tb.CreateType();
            //assemblyBuilder.Save("DynamicData.dll");
            return rowType;
        }

        public static IList GetDataList(DataService.DataSetData data)
        {
            if (data.Tables.Count() == 0)
                return null;

            DataService.DataTableInfo tableInfo = data.Tables[0];

            System.Type dataType = BuildDataObjectType(tableInfo.Columns, "MyDataObject");

            //ObservableCollection<DataObject> l = new ObservableCollection<DataObject>();

            var listType = typeof(ObservableCollection<>).MakeGenericType(new[] { dataType });
            var list = Activator.CreateInstance(listType);

            XDocument xd = XDocument.Parse(data.DataXML);
            var table = from row in xd.Descendants(tableInfo.TableName)
                        select row.Elements().ToDictionary(r => r.Name, r => r.Value);

            foreach (var r in table)
            {
                var rowData = Activator.CreateInstance(dataType) as DataObject;
                if (rowData != null)
                {
                    foreach (DataService.DataColumnInfo col in tableInfo.Columns)
                    {
                        if (r.ContainsKey(col.ColumnName) && col.DataTypeName != typeof(System.Byte[]).FullName && col.DataTypeName != typeof(System.Guid).FullName)
                            rowData.SetFieldValue(col.ColumnName.ToUpper(), r[col.ColumnName], true);
                        else if (r.ContainsKey(col.ColumnName) && col.DataTypeName == typeof(System.Byte[]).FullName)
                        {
                            rowData.SetFieldValue(col.ColumnName.ToUpper(), Convert.FromBase64String(r[col.ColumnName]), true);
                        }
                    }
                }
                listType.GetMethod("Add").Invoke(list, new[] { rowData });
            }
            ObservableCollection<DataObject> l = list as ObservableCollection<DataObject>;
            return list as  IList;
        }



        public static IList GetBindableObject(DataTable tableModel)
        {
            System.Type dataType = BuildDataObjectType("MyDataObject", tableModel);

            //ObservableCollection<DataObject> l = new ObservableCollection<DataObject>();

            var listType = typeof(ObservableCollection<>).MakeGenericType(new[] { dataType });
            var list = Activator.CreateInstance(listType);

            return list as IList;
        }


        public static void GetDataList(DataService.DataSetData data, IList list)
        {
            if (data.Tables.Count() == 0)
                return;

            DataService.DataTableInfo tableInfo = data.Tables[0];

           // System.Type dataType = BuildDataObjectType("MyDataObject", tableModel);

            //ObservableCollection<DataObject> l = new ObservableCollection<DataObject>();

            //var listType = typeof(ObservableCollection<>).MakeGenericType(new[] { dataType });
            //var list = Activator.CreateInstance(listType);

            //var list1 = list as IList;

            XDocument xd = XDocument.Parse(data.DataXML);
            var table = from row in xd.Descendants(tableInfo.TableName)
                        select row.Elements().ToDictionary(r => r.Name, r => r.Value);

            foreach (var r in table)
            {
                //var rowData = Activator.CreateInstance(dataType) as DataObject;

                var rowData = Activator.CreateInstance(list.GetType().GetGenericArguments()[0]) as DataObject;
                if (rowData != null)
                {
                    foreach (DataService.DataColumnInfo col in tableInfo.Columns)
                    {
                        if (r.ContainsKey(col.ColumnName) && col.DataTypeName != typeof(System.Byte[]).FullName && col.DataTypeName != typeof(System.Guid).FullName)
                            rowData.SetFieldValue(col.ColumnName.ToUpper(), r[col.ColumnName], true);
                        else if (r.ContainsKey(col.ColumnName) && col.DataTypeName == typeof(System.Byte[]).FullName)
                        {
                            rowData.SetFieldValue(col.ColumnName.ToUpper(), Convert.FromBase64String(r[col.ColumnName]), true);
                        }
                        else
                        {
                            rowData.SetBackupData(col.ColumnName, null);
                        }
                    }
                }
                //listType.GetMethod("Add").Invoke(list, new[] { rowData });
                list.Add(rowData);
            }
            //ObservableCollection<DataObject> l = list as ObservableCollection<DataObject>;
            //return list as IList;
        }

       




        public static System.Type BuildDataObjectType(string DataObjectName,DataTable tableModel)
        {
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("AprimoDynamicData"), AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DataModule");

            TypeBuilder tb = moduleBuilder.DefineType(DataObjectName,
                                                    TypeAttributes.Public |
                                                    TypeAttributes.Class,
                                                    typeof(DataObject));

            ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
            for(int i=0; i<tableModel.Columns.Count;i++)
            {
                DataColumn col = tableModel.Columns[i];
                string propertyName = col.ColumnName.Replace(' ', '_').ToUpper();
                Type dataType = col.DataType;
                if(dataType == typeof(int))
                {
                    dataType = typeof(Int32?);
                }
                if (dataType == typeof(decimal))
                {
                    dataType = typeof(Decimal?);
                }
                if (dataType == typeof(DateTime))
                {
                    dataType = typeof(DateTime?);
                }

                if (dataType != null)
                {
                    FieldBuilder fb = tb.DefineField("_" + propertyName, dataType, FieldAttributes.Private);
                    PropertyBuilder pb = tb.DefineProperty(propertyName, PropertyAttributes.None, dataType, null);
                    MethodBuilder getMethod = tb.DefineMethod("get_" + propertyName,
                                                                MethodAttributes.Public |
                                                                MethodAttributes.HideBySig |
                                                                MethodAttributes.SpecialName,
                                                                dataType,
                                                                Type.EmptyTypes);

                    ILGenerator ilgen = getMethod.GetILGenerator();
                    //Emit Get property, return _prop
                    ilgen.Emit(OpCodes.Ldarg_0);
                    ilgen.Emit(OpCodes.Ldfld, fb);
                    ilgen.Emit(OpCodes.Ret);
                    pb.SetGetMethod(getMethod);
                    MethodBuilder setMethod = tb.DefineMethod("set_" + propertyName,
                    MethodAttributes.Public |
                    MethodAttributes.HideBySig |
                    MethodAttributes.SpecialName,
                    null,
                    new Type[] { dataType });
                    ilgen = setMethod.GetILGenerator();
                    LocalBuilder localBuilder = ilgen.DeclareLocal(typeof(String[]));
                    //Emit set property, _Prop = value;
                    ilgen.Emit(OpCodes.Ldarg_0);
                    ilgen.Emit(OpCodes.Ldarg_1);
                    ilgen.Emit(OpCodes.Stfld, fb);

                    //Notify Change:
                    Type[] wlParams = new Type[] { typeof(string[]) };
                    MethodInfo notifyMI = typeof(DataObject).GetMethod("NotifyChange",
                    BindingFlags.NonPublic |
                    BindingFlags.Instance,
                    null,
                    CallingConventions.HasThis,
                    wlParams,
                    null);

                    //NotifyChange Property change
                    ilgen.Emit(OpCodes.Ldc_I4_1);
                    ilgen.Emit(OpCodes.Newarr, typeof(String));
                    ilgen.Emit(OpCodes.Stloc_0);
                    ilgen.Emit(OpCodes.Ldloc_0);
                    ilgen.Emit(OpCodes.Ldc_I4_0);
                    ilgen.Emit(OpCodes.Ldstr, propertyName);
                    ilgen.Emit(OpCodes.Stelem_Ref);
                    ilgen.Emit(OpCodes.Ldarg_0);
                    ilgen.Emit(OpCodes.Ldloc_0);
                    ilgen.EmitCall(OpCodes.Call, notifyMI, null); // call nodifyChange function

                    ilgen.Emit(OpCodes.Ret);
                    pb.SetSetMethod(setMethod);
                }
            }
            System.Type rowType = tb.CreateType();
            //assemblyBuilder.Save("DynamicData.dll");
            return rowType;
        }



    //    public static VDataService.DataSetData GetUpdatedDataSet(IEnumerable list, ObservableCollection<VDataService.DataTableInfo> tables)
    //    {
    //        VDataService.DataSetData data = new VDataService.DataSetData();
    //        data.Tables = tables;
    //        data.Tables = new ObservableCollection<DataSetInDataGrid.Silverlight.MyDataService.DataTableInfo>();
    //        foreach (MyDataService.DataTableInfo t in tables)
    //        {
    //            MyDataService.DataTableInfo table = new MyDataService.DataTableInfo { TableName = t.TableName };
    //            table.Columns = new ObservableCollection<DataSetInDataGrid.Silverlight.MyDataService.DataColumnInfo>();
    //            foreach (MyDataService.DataColumnInfo c in t.Columns)
    //            {
    //                table.Columns.Add(new MyDataService.DataColumnInfo{ColumnName= c.ColumnName, DataTypeName
    //            }
    //        }

    //        XElement root = new XElement("DataSet");
    //        foreach (DataObject d in list)
    //        {
    //            if (d.State != DataObject.DataStates.Unchanged )				
    //            {
    //                XElement row = new XElement("Data", new XAttribute("RowState", d.State.ToString()));
    //                PropertyInfo[] pis = d.GetType().GetProperties();
    //                foreach (PropertyInfo pi in pis)
    //                {
    //                    object val = pi.GetValue(d, null);
    //                    if(val != null)
    //                        row.Add(new XElement(pi.Name, val.ToString()));
    //                    else
    //                        row.Add(new XElement(pi.Name, ""));
    //                }
    //                root.Add(row);
    //            }
    //        }
    //        XDocument xdoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), root);
    //        data.DataXML = xdoc.ToString();
			
    //        return data;
    //    }

        internal static IList GetDataListSchema(DataTable defaultModel)
        {
            System.Type dataType = BuildDataObjectType("MyDataObject", defaultModel);
            //ObservableCollection<DataObject> l = new ObservableCollection<DataObject>();
            var listType = typeof(ObservableCollection<>).MakeGenericType(new[] { dataType });
            var list = Activator.CreateInstance(listType);
            return list as IList;
        }

        internal static void FillDataList(IList itemSource,  DataService.DataSetData data)
        {
            if (data.Tables.Count() == 0)
                return;

            DataService.DataTableInfo tableInfo = data.Tables[0];
            XDocument xd = XDocument.Parse(data.DataXML);
            var table = from row in xd.Descendants(tableInfo.TableName)
                        select row.Elements().ToDictionary(r => r.Name, r => r.Value);

            foreach (var r in table)
            {
                var rowData = Activator.CreateInstance(itemSource.GetType().GetGenericArguments()[0]) as DataObject;
                //DataObject rowData = new DataObject();
                if (rowData != null)
                {
                    foreach (DataService.DataColumnInfo col in tableInfo.Columns)
                    {
                        if (r.ContainsKey(col.ColumnName) && col.DataTypeName != typeof(System.Byte[]).FullName && col.DataTypeName != typeof(System.Guid).FullName)
                            rowData.SetFieldValue(col.ColumnName.ToUpper(), r[col.ColumnName], true);
                        else if (r.ContainsKey(col.ColumnName) && col.DataTypeName == typeof(System.Byte[]).FullName)
                        {
                            rowData.SetFieldValue(col.ColumnName.ToUpper(), Convert.FromBase64String(r[col.ColumnName]), true);
                        }
                        else
                        {
                            rowData.SetBackupData(col.ColumnName, null);
                        }
                    }
                }
                itemSource.Add(rowData );
            }

        }
    }
}